package com.divillysausages.dsair.state 
{
	import com.divillysausages.dsair.DSAir;
	import flash.events.Event;
	
	/**
	 * A finite state machine that you can use to move through states in your apps/objects
	 * @author Damian Connolly
	 */
	public class FSM implements IFSM
	{
		
		/********************************************************************/
		
		protected var m_states:Object 		= null; // the states that we're managing
		protected var m_currState:IState	= null; // the current state
		protected var m_nextState:IState	= null; // the next state
		protected var m_stateData:*			= null; // any data to pass to the state
		
		/********************************************************************/
		
		/**
		 * The current state that we're in. Will be null if we're not in any state
		 */
		public function get currState():IState { return this.m_currState; }
		
		/**
		 * The next state that we're going to. Will be null if we're not in middle of
		 * changing states.
		 */
		public function get nextState():IState { return this.m_nextState; }
		
		/********************************************************************/
		
		/**
		 * Creates the finite state machine
		 */
		public function FSM() 
		{
			// create our states object
			this.m_states = new Object;
		}
		
		/**
		 * Destroys the state machine and clears it for garbage collection
		 */
		public function destroy():void
		{
			// destroy all our states
			for ( var key:* in this.m_states )
			{
				this.m_states[key].destroy();
				this.m_states[key] = null;
				delete this.m_states[key];
			}
			
			// null our objects
			this.m_currState	= null;
			this.m_nextState	= null;
			this.m_states		= null;
			this.m_stateData	= null;
		}
		
		/**
		 * Adds a state to the state machine
		 * @param name The name that we want to add the state under
		 * @param state The state that we're adding
		 * @return If the state was addded
		 */
		public function addState( name:String, state:IState ):Boolean
		{
			// make sure we don't already have a state there
			if ( name in this.m_states )
			{
				DSAir.error( this, "There's already a state with the name '" + name + "' in this manager: " + this.m_states[name] );
				return false;
			}
			
			// add the state and set us as the manager
			this.m_states[name] = state;
			state.name			= name;
			state.manager		= this;
			
			// it worked
			return true;
		}
		
		/**
		 * Returns an IState based on a name
		 * @param name The name of the IState that we're looking for
		 * @return The IState, or null if we can't find it
		 */
		public function getState( name:String ):IState
		{
			return this.m_states[name];
		}
		
		/**
		 * Change the current state
		 * @param name The name of the State that we want to change to
		 * @param data Any data that we want to pass to the State
		 * @return If it worked. This will return false if we're already in that state, for example
		 */
		public function changeState( name:String, data:* = null ):Boolean
		{
			// check if we're already in this state
			if ( this.m_currState != null && this.m_currState.name == name )
			{
				DSAir.log( this, "Was asked to change to state '" + name + "', but we're already in that state" );
				return false;
			}
			
			// get our next state
			this.m_nextState = this.getState( name );
			if ( this.m_nextState == null )
			{
				DSAir.error( this, "Was aked to change to state '" + name + "', but we don't control that state", false );
				return false;
			}
			
			// hold the data
			this.m_stateData = data;
			
			// if we have a current state, then ask it to leave
			if ( this.m_currState != null )
			{
				// stop the current state
				this.m_currState.stop();
				
				// check if it's stopped (immediate, or if it needs some time (anim etc))
				if ( this.m_currState.isStopped )
					this._onStateStopping();
				else
					DSAir.stage.addEventListener( Event.ENTER_FRAME, this._onStateStopping );
			}
			else
			{
				// there's no current state, so just start the next state
				this.m_nextState.start();
				
				// check if it's started (immediate, or if it needs some time (anim etc))
				if ( this.m_nextState.isStarted )
					this._onStateStarting();
				else
					DSAir.stage.addEventListener( Event.ENTER_FRAME, this._onStateStarting );
			}
				
			// it worked
			return true;
		}
		
		/********************************************************************/
		
		// called when we're stopping a state - check if it's finished so we can bring
		// in the next one
		private function _onStateStopping( e:Event = null ):void
		{
			// if it's not stopped yet, just return
			if ( !this.m_currState.isStopped )
				return;
				
			// remove the event listener for this state
			DSAir.stage.removeEventListener( Event.ENTER_FRAME, this._onStateStopping );
				
			// call the onStop function
			this.m_currState.onStop();
			
			// if we have a next state, then bring it in
			if ( this.m_nextState != null )
			{
				this.m_nextState.start();
				
				// check if it's started (immediate, or it it needs some time)
				if ( this.m_nextState.isStarted )
					this._onStateStarting();
				else
					DSAir.stage.addEventListener( Event.ENTER_FRAME, this._onStateStarting );
			}
			else
				this.m_currState = null; // clear our curr state (so that if we recall it, the start() will fire)
		}
		
		// called when we're starting a state - check if it's finished
		private function _onStateStarting( e:Event = null ):void
		{
			// if it's not started yet, just return
			if ( !this.m_nextState.isStarted )
				return;
				
			// remove the event listener
			DSAir.stage.removeEventListener( Event.ENTER_FRAME, this._onStateStarting );
			
			// set the current state and clear the next
			this.m_currState = this.m_nextState;
			this.m_nextState = null;
			this.m_stateData = null;
				
			// call the onStart function
			this.m_currState.onStart( this.m_stateData );
		}
		
	}

}